﻿using System;
using System.Collections.Generic;
using System.Text;
using Vortex.Wrapper.Direct3D;
using Vortex.Wrapper;

namespace Vortex.Drawing {

	/// <summary>
	/// Collection of shader parameters. If there are no shader parameters explicitly set then default ones will be used.
	/// </summary>
	public sealed class ShaderParamCollection {		
		private Shader _owningShader;
		private DrawingDevice _device;
		private D3DXConstantTable _constTable;
		private ShaderType _shaderType;
		private Dictionary<string, ShaderParam> _params = new Dictionary<string, ShaderParam>();
		private ConstantCache _floatConstCache = new ConstantCache(sizeof(float) * 4);
		private ConstantCache _intConstCache = new ConstantCache(sizeof(int) * 4);
		private ConstantCache _boolConstCache = new ConstantCache(sizeof(int));

		/// <summary>
		/// Initialized an instance of <see cref="ShaderParams"/> class.
		/// </summary>
		/// <param name="shader">The owner shader.</param>
		/// <param name="constTable">The const table generated during shader compilation.</param>
		internal ShaderParamCollection(Shader shader, D3DXConstantTable constTable) {
			_owningShader = shader;
			_constTable = constTable;
			_device = shader.Device;
			_shaderType = shader is VertexShader ? ShaderType.VertexShader : ShaderType.PixelShader;
			
			unsafe {
				_constTable.OnFloatConstantChange = new UpdateFloatConstantHandler(this.OnFloatConstantChange);
				_constTable.OnIntConstantChange = new UpdateIntConstantHandler(this.OnIntConstantChange);
				_constTable.OnBoolConstantChange = new UpdateBoolConstantHandler(this.OnBoolConstantChange);
			}

			//lets setup defaults
			_constTable.SetDefaults();
		}

		/// <summary>
		/// Frees the params data.
		/// </summary>
		internal void FreeData() {
			_constTable.Dispose();
		}

		/// <summary>
		/// Refines cached constants. (Set up them to device)
		/// </summary>
		internal unsafe void RefineCachedConstants() {
			//set float constants
			if (!_floatConstCache.IsEmpty) {
				fixed (void* pData = &_floatConstCache.Data[0]) {
					_device.ApiRef.SetShaderConstantF(_shaderType, 0U, (float*)pData, _floatConstCache.RegisterCount);
				}
			}
			//set int constants
			if (!_intConstCache.IsEmpty) {
				fixed (void* pData = &_intConstCache.Data[0]) {
					_device.ApiRef.SetShaderConstantI(_shaderType, 0U, (int*)pData, _intConstCache.RegisterCount);
				}
			}
			//set bool constants
			if (!_boolConstCache.IsEmpty) {
				fixed (void* pData = &_boolConstCache.Data[0]) {
					_device.ApiRef.SetShaderConstantB(_shaderType, 0U, (int*)pData, _boolConstCache.RegisterCount);
				}
			}
		}

		/// <summary>
		/// Sets the value bytes.
		/// </summary>
		/// <param name="data">The value data.</param>
		/// <param name="byteCount">The byte count of value.</param>
		private unsafe void SetBytes(string paramName, void* data, int byteCount) {
			if (string.IsNullOrEmpty(paramName)) {
				throw new ArgumentException("paramName should be valid HLSL name existing for current shader", "paramName");
			}

			ShaderParam param;
			FindShaderParam(paramName, out param);

			HResult result = _constTable.SetConstantData(param.Handle, data, (uint)byteCount);
			if (result.IsFailed) {
				throw new ArgumentException("Parameter value type or size is wrong", "value");
			}

			/*
			if (_device.Renderer.ShaderParametersChanged(_owningShader)) {
				_constTable.SetConstantData(_device.ApiRef, param.Handle, data, (uint)byteCount);
			}
			*/
		}

		/// <summary>
		/// Finds the shader param description object
		/// </summary>
		/// <param name="paramName">Name of the parameter.</param>
		/// <param name="param">The found parameter.</param>
		private void FindShaderParam(string paramName, out ShaderParam param) {
			if (!_params.TryGetValue(paramName, out param)) {
				IntPtr handle = _constTable.FindConstantHandleByName(paramName);
				if (handle == IntPtr.Zero) {
					//throw new ArgumentException("paramName='{0}' is not in list of specified shader", paramName);
					throw new ArgumentException(string.Format("Shared parameter '{0}' is not specified (or used) in shader code", paramName));
				}
				param = new ShaderParam(handle);
			}
		}

		private unsafe void OnFloatConstantChange(uint startRegister, float* pData, uint vector4fCount) {
			_floatConstCache.SetValue(startRegister, pData, vector4fCount);

			/*
			float* xData = pData;
			for (int n = 0; n < vector4fCount; ++n) {
				Vortex.Debugging.Log.Details("[FLT]Register{0}: {1}, {2}, {3}, {4}", startRegister + n, xData[0], xData[1], xData[2], xData[3]);
				xData = xData + 4;
			}
			*/
			
			if (_device.Renderer.ShaderParametersChanged(_owningShader)) {
				_device.ApiRef.SetShaderConstantF(_shaderType, startRegister, pData, vector4fCount);
			}
		}

		private unsafe void OnIntConstantChange(uint startRegister, int* pData, uint vector4iCount) {
			_intConstCache.SetValue(startRegister, pData, vector4iCount);

			if (_device.Renderer.ShaderParametersChanged(_owningShader)) {
				_device.ApiRef.SetShaderConstantI(_shaderType, startRegister, pData, vector4iCount);
			}
		}

		private unsafe void OnBoolConstantChange(uint startRegister, int* pData, uint boolCount) {
			_boolConstCache.SetValue(startRegister, pData, boolCount);

			if (_device.Renderer.ShaderParametersChanged(_owningShader)) {
				_device.ApiRef.SetShaderConstantB(_shaderType, startRegister, pData, boolCount);
			}
		}

		#region Public interface

		/// <summary>
		/// Sets the default constant parameters for shader (defined in text).
		/// </summary>
		public void SetDefaults() {
			_constTable.SetDefaults();
		}

		/// <summary>
		/// Determines whether specified parameter exists in collection.
		/// </summary>
		/// <param name="paramName">Name of the shader param.</param>
		/// <returns>
		/// 	<c>true</c> if paramter exists in collection; otherwise, <c>false</c>.
		/// </returns>
		public bool Contains(string paramName) {
			ShaderParam param;
			if (!_params.TryGetValue(paramName, out param)) {
				IntPtr handle = _constTable.FindConstantHandleByName(paramName);
				return handle != IntPtr.Zero;
			} else {
				return true;
			}
		}

		/// <summary>
		/// Sets the specified shader parameters.
		/// </summary>
		/// <param name="paramName">Name of the parameter.</param>
		/// <param name="value">The float value.</param>
		public void Set(string paramName, float value) {
			unsafe {
				SetBytes(paramName, &value, sizeof(float));
			}
		}

		/// <summary>
		/// Sets the specified shader parameters.
		/// </summary>
		/// <param name="paramName">Name of the parameter.</param>
		/// <param name="value">The <see cref="Vector2"/> value.</param>
		public void Set(string paramName, Vector2 value) {
			unsafe {
				SetBytes(paramName, &value, sizeof(Vector2));
			}
		}

		/// <summary>
		/// Sets the specified shader parameters.
		/// </summary>
		/// <param name="paramName">Name of the parameter.</param>
		/// <param name="value">The <see cref="Vector3"/> value.</param>
		public void Set(string paramName, Vector3 value) {
			unsafe {
				SetBytes(paramName, &value, sizeof(Vector3));
			}
		}

		/// <summary>
		/// Sets the specified shader parameters.
		/// </summary>
		/// <param name="paramName">Name of the parameter.</param>
		/// <param name="value">The <see cref="Vector4"/> value.</param>
		public void Set(string paramName, Vector4 value) {
			unsafe {
				SetBytes(paramName, &value, sizeof(Vector4));
			}
		}

		/// <summary>
		/// Sets the specified shader parameters.
		/// </summary>
		/// <param name="paramName">Name of the parameter.</param>
		/// <param name="value">The <see cref="Transform"/> value.</param>
		public void Set(string paramName, Transform2D value) {
			unsafe {
				SetBytes(paramName, &value, sizeof(Transform2D));
			}
		}

		/// <summary>
		/// Sets the specified shader parameters.
		/// </summary>
		/// <param name="paramName">Name of the parameter.</param>
		/// <param name="value">The <see cref="Matrix"/> value.</param>
		public void Set(string paramName, Matrix value) {
			unsafe {
				SetBytes(paramName, &value, sizeof(Matrix));
			}
		}

		/// <summary>
		/// Sets the specified shader parameters.
		/// </summary>
		/// <param name="paramName">Name of the parameter.</param>
		/// <param name="value">The <see cref="Rect"/> value.</param>
		public void Set(string paramName, Rect value) {
			unsafe {
				SetBytes(paramName, &value, sizeof(Rect));
			}
		}

		/// <summary>
		/// Sets the specified shader parameters.
		/// </summary>
		/// <param name="paramName">Name of the parameter.</param>
		/// <param name="value">The <see cref="Rect"/> value.</param>
		public void Set(string paramName, ColorU value) {
			unsafe {
				float* values = stackalloc float[4];
				values[0] = value.Rf;
				values[1] = value.Gf;
				values[2] = value.Bf;
				values[3] = value.Af;
				SetBytes(paramName, values, sizeof(float) * 4);
			}
		}

		#endregion

    }
}
